﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MacomberMapSystem.Common.Database;
using System.Xml;
using MacomberMapSystem.Common.CIM;
using System.Data.Common;
using System.IO;
using System.Threading;
using MacomberMapSystem.Common.Serialization;
using MacomberMapSystem.Common.CIM.Direct;
using System.Data.SQLite;
using System.ComponentModel;
using System.Diagnostics;
using Ionic.Zip;

namespace MacomberMapSystem.Common.Processing
{
    /// <summary>
    /// This class is responsible for comparing two CIM models
    /// </summary>
    public class CIM_DifferenceReport : CIM_Processor
    {
        #region Variable declarations

        /// <summary>Whether to compare contingencies</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldValue), Category("Comparisons"), Description("Whether to compare contingencies")]
        public bool CompareContingencies { get; set; }

        /// <summary>Whether to run the comparison tool</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldValue), Category("Comparisons"), Description("Whether to run the comparison tool")]
        public bool RunComparisonTool { get; set; }
        
        #endregion

        #region Initalization
        /// <summary>
        /// Initialize our new model comparer
        /// </summary>
        /// <param name="NamedParameters"></param>
        /// <param name="xElem"></param>
        /// <param name="Repository"></param>
        public CIM_DifferenceReport(XmlElement xElem, Dictionary<String,Object>NamedParameters, MM_Repository Repository):
            base(xElem,NamedParameters,Repository)
        {
            Total = 0;
        }

        #endregion

        #region Importing of deltas from sharepoint
        /// <summary>
        /// Read our collection of delta files from sharepoint
        /// </summary>
        /// <param name="Repository"></param>
        public static void ReadDeltasFromSharepoint(MM_Repository Repository)
        {
            using (DbCommand dCmd = Repository.Db.CreateCommand("UPDATE MM_DATABASE_MODEL SET comparison_additions=null,  comparison_changes=null,  comparison_deletions=null"))
                dCmd.ExecuteNonQuery();
            foreach (MM_Database_Model Model in Repository.Model_Collection.Values)
                if (Model.ModelClass.StartsWith("CIM_") && Model.ModelCategory == "Current")
                {
                    String[] DirectoriesToSearch;
                    if (Model.ValidStart.Year < 2012)
                        DirectoriesToSearch = new string [] {@"[SharepointDirectory]\" + Model.ModelClass + "\\" + Model.ModelClass.Replace("_0", "_").Replace('_', ' ') + ".0 Models and Reports\\" + Model.FullClass};
                    else
                    {
                        String Month = Model.Name.Substring(4, 3);
                        DirectoriesToSearch = new string[] {
                        @"[SharepointDirectory]\" + Model.ModelClass + "_" + Month + "\\CIM " + Month + " Models and Reports\\CIM_" + Month + "_ML" + Model.FullClass.Substring(7),
                        @"[SharepointDirectory]\" + Model.ModelClass + "_" + Month + "\\CIM_" + Month + "_Models_and_Reports\\CIM_" + Month + "_ML" + Model.FullClass.Substring(7),
                        @"[SharepointDirectory]\" + Model.ModelClass + "_" + Month + "\\CIM_" + Month + "_ML" + Model.FullClass.Substring(7)};
                    }
            foreach (String DirectoryToSearch in DirectoriesToSearch)
                    if (!Directory.Exists(DirectoryToSearch))
                        Console.WriteLine("Unable to locate directory " + DirectoryToSearch);
                    else
                    {
                        Console.WriteLine("Found directory " + DirectoryToSearch);
                        foreach (FileInfo fI in new DirectoryInfo(DirectoryToSearch).GetFiles("NMMS_Model*Compare_HTML.zip"))
                            try
                            {
                                using (ZipFile zF = new ZipFile(fI.FullName))
                                    foreach (ZipEntry Entry in zF.Entries)
                                        if (Path.GetFileName(Entry.FileName) == "difference.xml")
                                        {
                                            XmlDocument xDoc = new XmlDocument();
                                            using (Stream ReadStream = Entry.OpenReader())
                                                xDoc.Load(ReadStream);
                                            Model.Comparison_Additions = Model.Comparison_Changes = Model.Comparison_Deletions = 0;
                                            foreach (XmlElement xDiff in xDoc.DocumentElement.ChildNodes)
                                                if (xDiff["ChangeItem"] != null || (xDiff["ChangeType"] != null && xDiff["ChangeType"].InnerText == "E"))
                                                    if (xDiff["DifferenceType"].InnerText == "A")
                                                        Model.Comparison_Additions++;
                                                    else if (xDiff["DifferenceType"].InnerText == "D")
                                                        Model.Comparison_Deletions++;
                                                    else if (xDiff["DifferenceType"].InnerText == "C")
                                                        Model.Comparison_Changes++;
                                            Model.UpdateRdb(Repository);
                                        }
                            }
                            catch (Exception ex)
                            {
                                Console.WriteLine("Error processing " + fI.FullName + ": " + ex.ToString());
                            }}
                }

            
        }
        #endregion



        #region Execution
        /// <summary>
        /// Initiate the processor
        /// </summary>
        /// <param name="DbConn"></param>
        public override enumProcessState InitiateProcess(MM_Database_Connector DbConn)
        {
            MM_Database_Model Model = NamedParameters["Model"] as MM_Database_Model;
            MM_Database_Model OldModel = NamedParameters["OldModel"] as MM_Database_Model;
            if (!Model.EnsureOpened())
                return enumProcessState.FailedWithErrors;
            if (!OldModel.EnsureOpened())
                return enumProcessState.FailedWithErrors;

            //Make sure we ensure element counts are there
            if (Model.ElementCount == 0)
                Model.LoadSummaryTable();
            if (OldModel.ElementCount == 0)
                OldModel.LoadSummaryTable();
            this.Total = Model.ElementCount + OldModel.ElementCount + Model.Attributes.Count + OldModel.Attributes.Count + Model.Types.Count + OldModel.Types.Count;


            //Write out our XML document
            using (XmlTextWriter xWrite = new XmlTextWriter(Path.Combine(ProcessString(TargetFolder), ProcessString(TargetFileName)), Encoding.UTF8))
            {
                //Write out our header text
                xWrite.Formatting = Formatting.Indented;
                xWrite.WriteStartDocument();
                xWrite.WriteComment("\n\tNaming scheme:\n\tDifference types:\n\t\tA - Added\n\t\tD - Deleted\n\t\tC - Changed\n\tChange types:\n\t\tE - Element\n\t\tA - Attribute\n\t\tL - Link\n\t\tT - Type\n\t\tAT - Attribute-type linkage");
                xWrite.WriteStartElement("Differences");
                xWrite.WriteAttributeString("OldModel", OldModel.CIMFileName);
                xWrite.WriteAttributeString("NewModel", Model.CIMFileName);
                xWrite.WriteAttributeString("xmlns:rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
                xWrite.WriteAttributeString("xmlns:cim", "http://iec.ch/TC57/2006/CIM-schema-cim10#");
                xWrite.WriteAttributeString("xmlns:spc", "http://www.siemens-ptd/SHIMM1.0#");
                xWrite.WriteAttributeString("xmlns:etx", "http://www.ercot.com/CIM11R0/2008/2.0/extension#");


                //First, compare our attributes
                int AddedAttributes = 0, RemovedAttributes = 0;
                foreach (String Attr in Model.Attributes)
                    if (!OldModel.Attributes.Contains(Attr))
                    {
                        WriteDifference(xWrite, "DifferenceType", "A", "ChangeItem", Attr, "ChangeType", "A");
                        AddedAttributes++;
                        Current++;
                    }
                foreach (String Attr in OldModel.Attributes)
                    if (!Model.Attributes.Contains(Attr))
                    {
                        WriteDifference(xWrite, "DifferenceType", "D", "ChangeItem", Attr, "ChangeType", "D");
                        RemovedAttributes++;
                        Current++;
                    }

                if (AddedAttributes != 0)
                    ProcessLog.AppendLine(AddedAttributes.ToString("#,##0") + " attributes were added.");
                if (RemovedAttributes != 0)
                    ProcessLog.AppendLine(RemovedAttributes.ToString("#,##0") + " attributes were removed.");


                //Next, compare our types   
                int AddedTypes = 0, RemovedTypes = 0, AddedTypeToAttributeMappings = 0, RemovedTypeToAttributeMappings = 0;

                for (int OldType = 0; OldType < OldModel.Types.Count; OldType++)
                {
                    if (!Model.Types.Contains(OldModel.Types[OldType]))
                    {
                        WriteDifference(xWrite, "DifferenceType", "D", "ChangeItem", OldModel.Types[OldType], "ChangeType", "T");
                        RemovedTypes++;
                    }
                    else
                    {
                        //Pull in our list of attributes, and compare
                        List<String> OldAttr = new List<string>();
                        List<String> CurAttr = new List<string>();
                        foreach (Int16 OldAttrVal in OldModel.AttributesByType[OldType])
                            OldAttr.Add(OldModel.Attributes[OldAttrVal]);
                        foreach (Int16 CurAttrVal in Model.AttributesByType[Model.Types.IndexOf(OldModel.Types[OldType])])
                            CurAttr.Add(Model.Attributes[CurAttrVal]);
                        foreach (String OldAttrVal in OldAttr)
                            if (!CurAttr.Contains(OldAttrVal))
                            {
                                RemovedTypeToAttributeMappings++;
                                WriteDifference(xWrite, "DifferenceType", "D", "ChangeItem", OldAttrVal, "ChangeType", "AT");
                            }
                        foreach (String CurAttrVal in CurAttr)
                            if (!OldAttr.Contains(CurAttrVal))
                            {
                                WriteDifference(xWrite, "DifferenceType", "A", "ChangeItem", CurAttrVal, "ChangeType", "AT");
                                AddedTypeToAttributeMappings++;
                            }
                    }
                    Current++;
                }
                foreach (String CurAttr in Model.Types)
                    if (!OldModel.Types.Contains(CurAttr))
                    {
                        WriteDifference(xWrite, "DifferenceType", "A", "ChangeItem", CurAttr, "ChangeType", "T");
                        AddedTypes++;
                        Current++;
                    }

                if (AddedTypes != 0)
                    ProcessLog.AppendLine(AddedTypes.ToString("#,##0") + " Types were added.");
                if (RemovedTypes != 0)
                    ProcessLog.AppendLine(RemovedTypes.ToString("#,##0") + " Types were removed.");
                if (AddedTypeToAttributeMappings != 0)
                    ProcessLog.AppendLine(AddedTypeToAttributeMappings.ToString("#,##0") + " Types to attribute mappings were added.");
                if (RemovedTypeToAttributeMappings != 0)
                    ProcessLog.AppendLine(RemovedTypeToAttributeMappings.ToString("#,##0") + " Types to attribute mappings were Removed.");




                //Now, compare our elements one by one
                List<CIM_RdfID> ForComparison = new List<CIM_RdfID>();
                List<CIM_RdfID> MissingFromFirst = new List<CIM_RdfID>();

                //First, build our list of first elements
                Dictionary<CIM_RdfID, byte[]> FirstElemInfo = new Dictionary<CIM_RdfID, byte[]>(OldModel.ElementCount);
                using (ElementRetriever kvp = new ElementRetriever(OldModel))
                    while (kvp.MoveNext())
                    {
                        FirstElemInfo.Add(kvp.Key, kvp.Value);
                        Current++;
                    }


                using (ElementRetriever NewElemInfo = new ElementRetriever(Model))
                    while (NewElemInfo.MoveNext())
                    {
                        if (!FirstElemInfo.ContainsKey(NewElemInfo.Key))
                            MissingFromFirst.Add(NewElemInfo.Key);
                        else if (!BytesEqual(FirstElemInfo[NewElemInfo.Key], NewElemInfo.Value))
                            ForComparison.Add(NewElemInfo.Key);
                        FirstElemInfo.Remove(NewElemInfo.Key);
                        this.Current++;
                    }


                this.Total = MissingFromFirst.Count + ForComparison.Count + FirstElemInfo.Count;
                this.Current = 0;

                //Write out all additions (elements in new, missing in old)
                int AddedElements = 0;
                foreach (CIM_RdfID InSecond in MissingFromFirst)
                {
                    CIM_Element Elem = Model.FromRdfID(InSecond);
                    if (!Elem.TypeName.Contains(":Contingency") || CompareContingencies)
                    {
                        WriteDifference(xWrite, Elem, true, "DifferenceType", "A", "InstanceRdfID", Elem.rdfID.ToString(), "ClassType", Elem.TypeName, "Path", Elem.ElementPath, "ChangeType", "E");
                        AddedElements++;
                    }
                    Current++;
                }
                ProcessLog.AppendLine(AddedElements.ToString("#,##0") + " elements were added.");

                //Write out all deletions (elements in old, missing in new)
                int RemovedElements = 0;
                foreach (CIM_RdfID InFirst in FirstElemInfo.Keys)
                {
                    CIM_Element Elem = OldModel.FromRdfID(InFirst);
                    if (!Elem.TypeName.Contains(":Contingency") || CompareContingencies)
                    {
                        WriteDifference(xWrite, Elem, false, "DifferenceType", "D", "InstanceRdfID", Elem.rdfID.ToString(), "ClassType", Elem.TypeName, "Path", Elem.ElementPath, "ChangeType", "E");
                        RemovedElements++;
                    }
                    Current++;
                }


                ProcessLog.AppendLine(RemovedElements.ToString("#,##0") + " elements were removed.");

                //Write all attribute changes
                int AttributeChanged = 0;
                foreach (CIM_RdfID rdfID in ForComparison)
                {
                    CIM_Element Elem = Model.FromRdfID(rdfID);
                    if (!Elem.TypeName.Contains(":Contingency") || CompareContingencies)
                        if (CompareAttributes(OldModel.FromRdfID(rdfID), Elem, xWrite))
                            AttributeChanged++;
                    Current++;
                }

                if (AttributeChanged > 0)
                    ProcessLog.AppendLine(AttributeChanged.ToString("#,##0") + " elements had attribute changes.");
                
                //Update our model with this information
                if (Model.ID != int.MinValue)
                {
                    Model.Comparison_Additions = AddedElements;
                    Model.Comparison_Changes = AttributeChanged;
                    Model.Comparison_Deletions = RemovedElements;
                    Model.UpdateRdb(Repository);
                }
            }

            //Make our directory if needed
            String OutputFolder = Path.Combine(TargetFolder, Path.GetFileNameWithoutExtension(TargetFileName));
            if (!Directory.Exists(OutputFolder))
                Directory.CreateDirectory(OutputFolder);

            //Perform our comparison, and compress our output.
            Results_Comparer.Program.WriteComparisonXml(OutputFolder, Path.Combine(TargetFolder, TargetFileName));
            CIM_OneLineValidator.WriteOutArchive(OutputFolder, OutputFolder + ".zip");


            


            return this.State = enumProcessState.Completed;
        }

        /// <summary>
        /// This class provides an enumerator to retrieve elements
        /// </summary>
        private class ElementRetriever : IEnumerator<KeyValuePair<CIM_RdfID, byte[]>>
        {
            #region Variable declarations
            /// <summary>The SQLite data reader, if any</summary>
            private SQLiteDataReader sRd = null;

            /// <summary>The SQLite command, if any</summary>
            private SQLiteCommand sCmd = null;

            /// <summary>Our enumerator for direct ones</summary>
            private IEnumerator<KeyValuePair<CIM_RdfID, CIM_Element>> DirectElems = null;

            /// <summary>Our current key</summary>
            public CIM_RdfID Key = null;

            /// <summary>Our current value</summary>
            public byte[] Value = new byte[0];
            #endregion

            #region Initialization
            /// <summary>
            /// Initialize an element retriever depending on what kind it is
            /// </summary>
            /// <param name="Model"></param>
            public ElementRetriever(MM_Database_Model Model)
            {
                if (Model is CIM_DirectModel)
                    DirectElems = (Model as CIM_DirectModel).Elements.GetEnumerator();
                else                
                    sRd = (sCmd = new SQLiteCommand("Select [rdfID],[Checksum] FROM CIM_Elements", Model.sConn)).ExecuteReader();
            }
            #endregion

            #region Enumeration handling
            /// <summary>
            /// Return the current value
            /// </summary>
            public KeyValuePair<CIM_RdfID, byte[]> Current
            {
                get
                {
                    return new KeyValuePair<CIM_RdfID,byte[]>(Key,Value);
                }                
            }

            /// <summary>
            /// Dispose our enumerator
            /// </summary>
            public void Dispose()
            {
                if (sRd != null)
                {
                    sRd.Close();
                    sRd.Dispose();
                    sCmd.Dispose();
                }
                else
                    DirectElems.Dispose();
            }

            /// <summary>
            /// Report the current value
            /// </summary>
            object System.Collections.IEnumerator.Current
            {
                get
                {
                    return new KeyValuePair<CIM_RdfID, byte[]>(Key, Value);
                }
            }

            /// <summary>
            /// Move to the next element
            /// </summary>
            /// <returns></returns>
            public bool MoveNext()
            {
                
                if (sRd != null)
                    if (sRd.Read())
                    {
                        Key = new CIM_RdfID((byte[])sRd["rdfID"]);
                        Value = (byte[])sRd["Checksum"];
                        return true;                        
                    }
                    else
                        return false;
                else
                    if (DirectElems.MoveNext())
                    {
                        Key = DirectElems.Current.Key;
                        Value = DirectElems.Current.Value.CheckSum;
                        return true;
                    }
                    else
                        return false;
            }

            /// <summary>
            /// Reset our counter
            /// </summary>
            public void Reset()
            {
                if (sRd != null)
                {
                    sRd.Close();
                    sRd.Dispose();
                    sRd = sCmd.ExecuteReader();
                }
                else
                    DirectElems.Reset();
                Key = null;
                Value = new byte[0];
            }
            #endregion
        }


        /// <summary>
        /// Compare the attributes between elements one and two
        /// </summary>
        /// <param name="OldValue">The old CIM element</param>
        /// <param name="NewValue">The new CIM element</param>
        /// <param name="xWrite">The XML text writer</param>
        private bool CompareAttributes(CIM_Element OldValue, CIM_Element NewValue, XmlTextWriter xWrite)
        {
            //First, build our list of attributes that are going to be handled
            Dictionary<String, bool> Attributes = new Dictionary<string, bool>(StringComparer.CurrentCultureIgnoreCase);
            Dictionary<String, String> Attr1 = OldValue.Attributes;
            Dictionary<String, String> Attr2 = NewValue.Attributes;
            bool HadChange = false;
            bool WroteHeader = false;

            foreach (String attrName in Attr1.Keys)
                if (!attrName.EndsWith(")") && !Attributes.ContainsKey(attrName))
                    Attributes.Add(attrName, Attr1.ContainsKey(attrName + " (1)"));

            foreach (String attrName in Attr2.Keys)
                if (!attrName.EndsWith(")"))
                    if (!Attributes.ContainsKey(attrName))
                        Attributes.Add(attrName, Attr1.ContainsKey(attrName + " (1)"));
                    else
                        Attributes[attrName] = Attributes[attrName] || Attr2.ContainsKey(attrName + " (1)");


            //Now, go through and compare each element
            foreach (KeyValuePair<String, bool> Attr in Attributes)
                if (!Attr.Value)
                {
                    if (!Attr1.ContainsKey(Attr.Key))
                    {
                        WriteAttributeDifferenceHeader(xWrite, NewValue, OldValue, Attr.Key, ref WroteHeader);
                        WriteSingleMissingAttribute(xWrite, true, NewValue, Attr.Key, NewValue[Attr.Key]);
                        HadChange = true;
                    }
                    else if (!Attr2.ContainsKey(Attr.Key))
                    {
                        WriteAttributeDifferenceHeader(xWrite, NewValue, OldValue, Attr.Key, ref WroteHeader);
                        WriteSingleMissingAttribute(xWrite, false, OldValue, Attr.Key, OldValue[Attr.Key]);
                        HadChange = true;
                    }
                    else if (Attr1[Attr.Key] != Attr2[Attr.Key])
                    {
                        WriteAttributeDifferenceHeader(xWrite, NewValue, OldValue, Attr.Key, ref WroteHeader);
                        WriteSingleDifferentAttribute(xWrite, NewValue, OldValue, Attr.Key, Attr1[Attr.Key], Attr2[Attr.Key]);
                        HadChange = true;
                    }
                }
                else
                {
                    //Pull in our values into lists, so we can compare them
                    String InVal;
                    int a = 0;
                    List<String> Val1 = new List<string>();
                    List<String> Val2 = new List<string>();
                    while (Attr1.TryGetValue(Attr.Key + (a == 0 ? "" : " (" + a.ToString() + ")"), out InVal))
                    {
                        Val1.Add(InVal);
                        a++;
                    }
                    a = 0;
                    while (Attr2.TryGetValue(Attr.Key + (a == 0 ? "" : " (" + a.ToString() + ")"), out InVal))
                    {
                        Val2.Add(InVal);
                        a++;
                    }

                    foreach (String str in Val1)
                        if (!Val2.Contains(str))
                        {
                            WriteAttributeDifferenceHeader(xWrite, NewValue, OldValue, Attr.Key, ref WroteHeader);
                            WriteSingleMissingAttribute(xWrite, false, OldValue, Attr.Key, str);
                            HadChange = true;
                        }
                    foreach (String str in Val2)
                        if (!Val1.Contains(str))
                        {
                            WriteAttributeDifferenceHeader(xWrite, OldValue, NewValue, Attr.Key, ref WroteHeader);
                            WriteSingleMissingAttribute(xWrite, true, NewValue, Attr.Key, str); ;
                            HadChange = true;
                        }
                }
            if (HadChange)
                xWrite.WriteEndElement();
            return HadChange;
        }

        /// <summary>
        /// Write out the needed information for a missing attribute
        /// </summary>
        /// <param name="xWrite"></param>        
        /// <param name="AttrIn"></param>
        /// <param name="AttrOut"></param>
        /// <param name="AttributeName"></param>        
        /// <param name="WroteHeader"></param>
        private void WriteAttributeDifferenceHeader(XmlTextWriter xWrite, CIM_Element AttrIn, CIM_Element AttrOut, string AttributeName, ref bool WroteHeader)
        {
            if (WroteHeader)
                return;
            else
                WroteHeader = true;
            xWrite.WriteStartElement("Difference");
            xWrite.WriteElementString("DifferenceType", "C");
            xWrite.WriteElementString("InstanceRdfID", AttrIn.rdfID.ToString());
            xWrite.WriteElementString("ClassType", AttrIn.TypeName);            
            xWrite.WriteElementString("Path", AttrIn.ElementPath);
        }

        /// <summary>
        /// Write out a single attribute difference
        /// </summary>
        /// <param name="xWrite"></param>
        /// <param name="InCurrent"></param>
        /// <param name="AttrIn"></param>
        /// <param name="AttributeName"></param>
        /// <param name="Attr"></param>
        private void WriteSingleMissingAttribute(XmlTextWriter xWrite, bool InCurrent, CIM_Element AttrIn, string AttributeName, String Attr)
        {
            xWrite.WriteStartElement("ChangeItem");
            xWrite.WriteElementString("Item", AttributeName);            
            xWrite.WriteElementString("ChangeType", Attr.StartsWith("#") ? "L" : "A");
            xWrite.WriteElementString(InCurrent ? "NewValue" : "OldValue", Attr);
            if (Attr.StartsWith("#"))
                xWrite.WriteElementString(InCurrent ? "NewLinkPath" : "OldLinkPath", AttrIn.CIM.FromRdfID(Attr.Substring(1)).ElementPath);
            xWrite.WriteElementString(InCurrent ? "OldValue" : "NewValue", "");
            xWrite.WriteEndElement();
        }
        

        /// <summary>
        /// Write out a single attribute difference
        /// </summary>
        /// <param name="xWrite"></param>
        /// <param name="NewElem"></param>
        /// <param name="NewElemValue"></param>
        /// <param name="OldElem"></param>
        /// <param name="OldElemValue"></param>        
        /// <param name="AttributeName"></param>
        private void WriteSingleDifferentAttribute(XmlTextWriter xWrite, CIM_Element NewElem, CIM_Element OldElem, string AttributeName, string OldElemValue, string NewElemValue)
        {
            xWrite.WriteStartElement("ChangeItem");
            xWrite.WriteElementString("Item", AttributeName);
            xWrite.WriteElementString("ChangeType", (OldElemValue.StartsWith("#") || NewElemValue.StartsWith("#")) ? "L" : "A");
            xWrite.WriteElementString("NewValue", NewElemValue);
            if (NewElemValue.StartsWith("#"))
                xWrite.WriteElementString("NewLinkPath", NewElem.CIM.FromRdfID(NewElemValue.Substring(1)).ElementPath);
            xWrite.WriteElementString("OldValue", OldElemValue);
            if (OldElemValue.StartsWith("#"))
                xWrite.WriteElementString("OldLinkPath", OldElem.CIM.FromRdfID(OldElemValue.Substring(1)).ElementPath);
            xWrite.WriteEndElement();
        }

        /// <summary>
        /// Determine whether two sequences of bytes are equal
        /// </summary>
        /// <param name="Bytes1">The first sequence of bytes</param>
        /// <param name="Bytes2">The second sequences of bytes</param>
        /// <returns></returns>
        private bool BytesEqual(byte[] Bytes1, byte[] Bytes2)
        {
            if (Bytes1.Length != Bytes2.Length)
                return false;
            for (int a = 0; a < Bytes1.Length; a++)
                if (Bytes1[a] != Bytes2[a])
                    return false;
            return true;
        }

        /// <summary>
        /// Write an attribute difference out
        /// </summary>
        /// <param name="xWrite">The target XML Text writer</param>
        /// <param name="AttributesToWrite">The collection of attributes to write</param>
        private void WriteDifference(XmlTextWriter xWrite, params String[] AttributesToWrite)
        {
            xWrite.WriteStartElement("Difference");
            for (int a = 0; a < AttributesToWrite.Length; a += 2)
                xWrite.WriteElementString(AttributesToWrite[a], AttributesToWrite[a + 1]);
            xWrite.WriteEndElement();
            xWrite.Flush();
        }

        /// <summary>
        /// Write an attribute difference out
        /// </summary>
        /// <param name="xWrite">The target XML Text writer</param>
        /// <param name="InCurrent"></param>
        /// <param name="Elem">The element which needs its parameters written out</param>
        /// <param name="AttributesToWrite">The collection of attributes to write</param>
        private void WriteDifference(XmlTextWriter xWrite, CIM_Element Elem,  bool InCurrent, params String[] AttributesToWrite)
        {
            xWrite.WriteStartElement("Difference");
            for (int a = 0; a < AttributesToWrite.Length; a += 2)
                xWrite.WriteElementString(AttributesToWrite[a], AttributesToWrite[a + 1]);
            //xWrite.WriteStartElement(Elem.TypeName);
            //xWrite.WriteElementString("rdf:ID", Elem.rdfID.ToString());
            if (InCurrent)
                try
                {
                    foreach (KeyValuePair<String, String> kvp in Elem.Attributes)
                        if (kvp.Key != "Type")
                            WriteSingleMissingAttribute(xWrite, InCurrent, Elem, kvp.Key, kvp.Value);
                }
                catch (Exception ex)
                {
                }
            xWrite.WriteEndElement();
        }
        #endregion

        /// <summary>
        /// Report the process name
        /// </summary>
        public override string ProcessName
        {
            get { return "Model Comparison"; }
        }
    }
}
